package objects;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import utils.AutoDealer;
import utils.FactoryUI;
import utils.PlayerFactory;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

public class Board {

	private static Board boardInst = null;
	private static final String LEFT = "L";
	public final int NUMBER_OF_ROOMS = 16;
	private Containable[] rooms = new Containable[NUMBER_OF_ROOMS];
	private BoardData bdGameData;
	private JsonArray jsActions;
	private int nCurrPlayer;
	private static Board boardBackUp;
	private int nMessageCount = 0;
	private int numberOfPlayersJoined;
	private int numberOfPlayersTotal;
	private boolean manualError;
	public List<String> JoinedPlayers;
	
	private Board()
	{
		rooms[0] = new MurderableRoom(new ArrayList<Player>(), Room.HALL, null);
		rooms[2] = new MurderableRoom(new ArrayList<Player>(), Room.LOUNGE, Weapon.ROPE);
		rooms[4] = new MurderableRoom(new ArrayList<Player>(), Room.DINING_ROOM, Weapon.CANDLESTICK);
		rooms[6] = new MurderableRoom(new ArrayList<Player>(), Room.KITCHEN, Weapon.WRENCH);
		rooms[8] = new MurderableRoom(new ArrayList<Player>(), Room.BALLROOM, Weapon.DAGGER);
		rooms[10] = new MurderableRoom(new ArrayList<Player>(), Room.CONSERVATORY, Weapon.LEAD_PIPE);
		rooms[12] = new MurderableRoom(new ArrayList<Player>(), Room.BILLIARD_ROOM, null);
		rooms[13] = new MurderableRoom(new ArrayList<Player>(), Room.LIBRARY, null);
		rooms[14] = new MurderableRoom(new ArrayList<Player>(), Room.STUDY, Weapon.REVOLVER);
		
		rooms[1] = new Containable(new ArrayList<Player>());
		rooms[3] = new Containable(new ArrayList<Player>());
		rooms[5] = new Containable(new ArrayList<Player>());
		rooms[7] = new Containable(new ArrayList<Player>());
		rooms[9] = new Containable(new ArrayList<Player>());
		rooms[11] = new Containable(new ArrayList<Player>());
		rooms[15] = new Containable(new ArrayList<Player>());
		bdGameData = null;
		jsActions = new JsonArray();
		JoinedPlayers = new ArrayList<String>();
		List<Character> characters = new ArrayList<Character>();
		for (int i = 0; i < Character.values().length; i++)
		{
			characters.add(Character.values()[i]);
		}
		rooms[0].setCharacters(characters);
		nCurrPlayer=0;
	}
	public static Board getInstance()
	{
		if (boardInst == null)
		{
			boardInst = new Board();
		}
		
	    return boardInst;
	 }
	
	public void resetInstance()
	{
		boardInst = new Board();
	}
	
	public BoardData getBdGameData() 
	{
		return bdGameData;
	}
	public String getCurrentRoomData(int hoveredRoom)
	{
		StringBuilder stbRoomPlayers = new StringBuilder();
		StringBuilder stbRoomChars = new StringBuilder();
		StringBuilder stbRoomWeapons = new StringBuilder();

		
		for (Player plCurr : this.rooms[hoveredRoom].getPlayers()) 
		{
			stbRoomPlayers.append("<li> " + plCurr.getPlayerName() + " </li>");
		}
		
		for (Character chCurr : this.rooms[hoveredRoom].getCharacters()) 
		{
			stbRoomChars.append("<li> " + chCurr.name() + " </li>");
		}
		
		if (this.rooms[hoveredRoom] instanceof MurderableRoom)
		{
			for (Weapon wpCurr : ((MurderableRoom) this.rooms[hoveredRoom]).getWeapons()) 
			{
				stbRoomWeapons.append("<li> " + wpCurr.name() + " </li>");
			}
		}
		
		return FactoryUI.getRoomContentHtml(stbRoomPlayers.toString(), stbRoomWeapons.toString(), stbRoomChars.toString());	
	}

	public int getCurrPlayer() 
	{
		return nCurrPlayer;
	}

	public String getRoomName(int nRoom) 
	{
		if (this.rooms[nRoom] instanceof MurderableRoom)
		{
			return ((MurderableRoom)this.rooms[nRoom]).getRoom().name();
		}
		else
		{
			return "CORRIDOR";
		}
	}
	public Player getCurrPlayerInstance()
	{
		return bdGameData.getPlayerByIndex(nCurrPlayer);
	}

	public int getCurrPlayersRoom() {
		return bdGameData.getPlayers().get(nCurrPlayer).getRoomNumber();
	}
	
	public Containable[] getRooms() 
	{
		return rooms;
	}
	
	public void backUp()
	{
		boardBackUp = new Board();
		
	    BoardData bdToAdd = new BoardData();
		List<Player> lstPlayers = new ArrayList<Player>();
		
		int i = 0;
		
		for (Player player : boardInst.getBdGameData().getPlayers()) {
			 lstPlayers.add(PlayerFactory.createPlayer(i, player.getPlayerName(), Character.values()[i], player.getType().toString(), i+1));
			i++;
		}
		
    	bdToAdd.setPlayers(lstPlayers);
        bdToAdd = AutoDealer.Deal(bdToAdd);
        bdToAdd.setType(boardInst.getBdGameData().getType());
		boardBackUp.setBdGameData(bdToAdd);
		boardBackUp.JoinedPlayers = new ArrayList<String>();
		for (Player player : boardBackUp.getBdGameData().getPlayers()) 
    	{
			// Enter the player to the room number roomNumber
			boardBackUp.getRooms()[0].participantEnter(player);
			player.setRoomNumber(0);
		}
	}
	
	/**
	 * Method responsible to move a player to his decided room in a cyclic way and both left or right according to his decision
	 * @param player - the player to move
	 * @param diceResult - the result from the dice
	 * @param playerDecision - l for left and r for right
	 */
	public void movePlayer(Player player, int diceResult, String playerDecision)
	{
		int newRoomNumber;
		
		// If the player decided to move left then going "up" in the room order
		if (playerDecision.toUpperCase().equals(LEFT))
		{
			newRoomNumber = (player.getRoomNumber() + diceResult + NUMBER_OF_ROOMS) % NUMBER_OF_ROOMS;
		}
		// Going "down" in the room order
		else
		{
			newRoomNumber = (player.getRoomNumber() - diceResult + NUMBER_OF_ROOMS) % NUMBER_OF_ROOMS;
		}
		move(player,newRoomNumber);		 
	}
	
	
	public void move(Player player, int newRoomNumber)
	{		
		// The player exit the room  
		rooms[player.getRoomNumber()].participantExit(player);
		// The player enters the room number 'newRoomNumber' 
		rooms[newRoomNumber].participantEnter(player);
		
		// Update the room number of player
		player.setRoomNumber(newRoomNumber);
		
		// Update the room number of character
		Character character = player.getCharacter();
		for (Containable containable : Board.getInstance().getRooms()) {
			
			// Check if the character is in this room
			if (containable.isCharacterIn(character))
			{
				containable.participantExit(character);
				// Enter the character to the room number roomNumber
				rooms[newRoomNumber].participantEnter(character);
				break;
			}
		}
		 
	}
	
	public static void restart() 
    {
		boardInst = new Board();
		
	    BoardData bdToAdd = new BoardData();
		List<Player> lstPlayers = new ArrayList<Player>();
		
		int i = 0;
		
		for (Player player : boardBackUp.getBdGameData().getPlayers()) {
			 lstPlayers.add(PlayerFactory.createPlayer(i, player.getPlayerName(), Character.values()[i], player.getType().toString(), i+1));
			i++;
		}
		
    	bdToAdd.setPlayers(lstPlayers);
        bdToAdd = AutoDealer.Deal(bdToAdd);
        bdToAdd.setType(boardBackUp.getBdGameData().getType());
        boardInst.setBdGameData(bdToAdd);
		for (Player player : boardInst.getBdGameData().getPlayers()) 
    	{
			// Enter the player to the room number roomNumber
			boardInst.getRooms()[0].participantEnter(player);
			player.setRoomNumber(0);
		}
		
		boardInst.resetActions();
		boardInst.JoinedPlayers = new ArrayList<String>();
		boardBackUp = boardInst;
	}
	
	public void resetActions() 
	{
		this.jsActions = new JsonArray();
	}
	
	public void setBdGameData(BoardData GameData) 
	{
		bdGameData = GameData;
	}
	
	public void setCurrPlayer(int nCurrPlayer) {
		this.nCurrPlayer = nCurrPlayer;
	}
	
	public void advanceTurn()
	{
		List<Player> players = this.getBdGameData().getPlayers();
		
		if (!this.getBdGameData().getPlayerByIndex(nCurrPlayer).isInTheGame())
		{
			rooms[getCurrPlayersRoom()].participantExit(getCurrPlayerInstance());
		}
		
		for (int i = 0; i < players.size() - 1; i++) 
    	{
				if (this.getBdGameData().getPlayerByIndex((nCurrPlayer + (i + 1)) % players.size()).isInTheGame())
					{
						this.nCurrPlayer = (nCurrPlayer + (i + 1)) % players.size();
						break;
					}
    	}
	}
	
	public int randomNumbers(int nMin, int nMax)
	{
		return nMin + (int)(Math.round(Math.random() * ((nMax - nMin))));
	}
	
 	public boolean onePlayerLeft()
 	{
 		int playersInTheGame = 0;
  	
 		for (Player player : boardInst.getBdGameData().getPlayers()) {
 			if (player.isInTheGame())
 			{
 				playersInTheGame++;
			}
 		}
  	
 		return (playersInTheGame == 1);
 	}
  
    public JsonArray getJsActions(int nActID)
    {
    	JsonArray ret = new JsonArray();
    	if (jsActions.size() > nActID)
    	{
    		ret.add(jsActions.get(nActID));
    	}
    	
  	  	return ret;
    }

    public Player getPlayerByName(String playerName)
    {
		for (Player player : this.bdGameData.getPlayers()) 
		{
			if (player.getPlayerName().equals(playerName))
			{
				return player;
			}
		}
		return null;
    }
    
	public void addJsActions(JsonObject jsAction)
	{
		jsAction.addProperty("objId", ++nMessageCount);
		this.jsActions.add(jsAction);
		
	}
	public int getNumberOfPlayersJoined() 
	{
		return numberOfPlayersJoined;
	}
	
	public int getNumberOfHumans() 
	{
		int nHumans = 0;
		if (this.bdGameData.getPlayers() == null)
		{
			nHumans = -1;
		}
		else
		{
			for (int i = 0; i < this.bdGameData.getPlayers().size(); i++)
			{
				if (this.bdGameData.getPlayers().get(i).getType().equals("HUMAN"))
				{
					nHumans++;
				}
			}
		}
		return nHumans;
	}
	
	public void setNumberOfPlayersJoined(int numberOfPlayersJoined) 
	{
		this.numberOfPlayersJoined = numberOfPlayersJoined;
	}
	public int getNumberOfPlayersTotal() {
		return numberOfPlayersTotal;
	}
	public void setNumberOfPlayersTotal(int numberOfPlayersTotal) {
		this.numberOfPlayersTotal = numberOfPlayersTotal;
	}
	public boolean isGameStarted() {
		if (numberOfPlayersJoined == 0)
		{
			return false;
		}
		
		return numberOfPlayersJoined == numberOfPlayersTotal;
	}
	public boolean isGameInitialized() {
		return numberOfPlayersJoined < numberOfPlayersTotal;
	}
	public void removeLastRestart()
	{
		
		JsonArray jsNew = new JsonArray();
		JsonObject jsAction = new JsonObject();
		jsAction.addProperty("code", "nop");
		jsNew.add(jsAction);
		for (int i = 1; i < this.jsActions.size(); i++)
		{
			jsNew.add(this.jsActions.get(i));
		}
		
		this.jsActions = jsNew;
	}
	public static void Nullify()
	{
		Board.boardInst = null;
		Board.boardBackUp = null;		
	}
	public boolean isManualError() {
		return manualError;
	}
	public void setManualError(boolean manualError) {
		this.manualError = manualError;
	}
	public int getNumberOfHumansInGame()
	{
		int nHumans = 0;
		if (this.bdGameData.getPlayers() == null)
		{
			nHumans = -1;
		}
		else
		{
			for (int i = 0; i < this.bdGameData.getPlayers().size(); i++)
			{
				if (this.bdGameData.getPlayers().get(i).getType().equals("HUMAN") && this.bdGameData.getPlayers().get(i).isInTheGame())
				{
					nHumans++;
				}
			}
		}
		return nHumans;
	}
}
